#define _CRT_SECURE_NO_WARNINGS
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
//第20讲：⾃定义类型：联合和枚举+第21讲：动态内存管理
//2. 枚举类型
//2.1 枚举类型的声明
//枚举顾名思义就是⼀⼀列举。
//把可能的取值⼀⼀列举。
//enum Day//星期
//{
//	Mon,	//0
//	Tues,	//1
//	Wed=5,	//5
//	Thur,	//6
//	Fri,	//7
//	Sat,	//8
//	Sun		//9
//};//{}中的内容是枚举类型的可能取值，也叫 枚举常量 。
//这些可能取值都是有值的，默认从0开始，依次递增1，当然在声明枚举类型的时候也可以赋初值。


//2.2 枚举类型的优点
//我们可以使⽤ #define 定义常量，为什么⾮要使⽤枚举？
//枚举的优点：
//1. 增加代码的可读性和可维护性
//比如写一个计算器:
//enum Option
//{
//	EXIT,	//0
//	ADD,	//1
//	SUB,	//2
//	MUL,	//3
//	DIV		//4
//};
//void menu()
//{
//	printf("0.exit  1.add\n");
//	printf("2.sub   3.mul\n");
//	printf("4.div        \n");
//}
//int main()
//{
//	menu();
//	int input = 0;
//	printf("请选择:>");
//	scanf("%d", &input);
//	switch (input)
//	{
//	case ADD://这样的话不需要写数字就能看出来这串代码是执行加法功能的，提升了可读性（case 1的可读性较差，不能立马反映出这是加法）
//		break;
//	case SUB:
//		break;
//	case MUL:
//		break;
//	case DIV:
//		break;
//	case EXIT:
//		break;
//	default:
//		break;
//	}
//	return 0;
//}

//2. 和#define定义的标识符⽐较枚举有类型检查，更加严谨。
//define定义的标识符在编译阶段直接把标识符替换成定义的数据
//枚举类型就是枚举类型，和整型类型不一样,不可以把枚举类型赋给一个int类型的变量，但是define定义的值可以赋给任何变量

//3. 便于调试，预处理阶段会删除 #define 定义的符号（#define不便于调试）

//4. 使⽤⽅便，⼀次可以定义多个常量

//5. 枚举常量是遵循作⽤域规则的，枚举声明在函数内，只能在函数内使⽤



//2.3 枚举类型的使⽤
//enum Color//颜⾊
//{
//	RED,	//0
//	GREEN,	//1
//	BLUE	//2
//};
//enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值
//那是否可以拿整数给枚举变量赋值呢？在C语⾔中是可以的，但是在C++是不⾏的，C++的类型检查⽐较严格。



//第22讲：动态内存管理
//不管是C还是C++都会大量使用，使用C或者C++实现数据结构的时候，也会使用动态内存管理
//1. 为什么要有动态内存分配
//我们已经掌握的内存开辟⽅式有：
//int val = 20;//在栈空间上开辟四个字节
//char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
//但是上述的开辟空间的⽅式有两个特点：
//• 空间开辟⼤⼩是固定的。
//• 数组在申明的时候，必须指定数组的⻓度，数组空间⼀旦确定了⼤⼩不能调整

//2. malloc和free	
//2.1 malloc
//void* malloc (size_t size);
//这个函数向内存申请⼀块连续可⽤的空间，并返回指向这块空间的指针。
//• 如果开辟成功，则返回⼀个指向开辟好空间的指针。
//• 如果开辟失败，则返回⼀个 NULL 指针，因此malloc的返回值⼀定要做检查。
//• 返回值的类型是 void* ，所以malloc函数并不知道开辟空间的类型，具体在使⽤的时候使⽤者⾃
//⼰来决定。
//• 如果参数 size 为0，malloc的⾏为是标准是未定义的，取决于编译器
//int main()
//{
//	int*p=(int*)malloc(20);//申请空间（申请的空间在内存中的堆区）
//	if (p == NULL)//判断返回的是不是空指针
//	{
//		perror("malloc");
//		return 1;
//	}
//	for (int i = 0; i < 5; i++)
//		p[i] = i + 1;//使用空间
//	//释放空间:
//	//2.2 free
//	//	C语⾔提供了另外⼀个函数free，专⻔是⽤来做动态内存的释放和回收的，函数原型如下：
//	//void free (void* ptr);free函数⽤来释放动态开辟的内存。
//	//• 如果参数 ptr 指向的空间不是动态开辟的，那free函数的⾏为是未定义的。
//	//	• 如果参数 ptr 是NULL指针，则函数什么事都不做。
//	free(p);//释放malloc申请的空间，权限返还给操作系统，此时p是野指针,注意：这里的p必须是你申请空间的起始地址
//	p = NULL;//所以我们需要把p置空
//	return 0;
//}



//3. calloc和realloc
//3.1 calloc
//原型:
//void* calloc (size_t num, size_t size);
//• 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间，并且把空间的每个字节初始化为0。
//• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
//int main()
//{
//	int*p=(int*)calloc(5, sizeof(int));
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	for (int i = 0; i < 5;i++)
//		printf("%d ", p[i]);
//	free(p);
//	p = NULL;
//	return 0;
//}//所以如果我们对申请的内存空间的内容要求初始化，那么可以很⽅便的使⽤calloc函数来完成任务



//3.2 realloc
//• realloc函数的出现让动态内存管理更加灵活。
//• 有时会我们发现过去申请的空间太⼩了，有时候我们⼜会觉得申请的空间过⼤了，那为了合理的使
//⽤内存，我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤
//⼩的调整。
//函数原型如下：
//void* realloc(void* ptr, size_t size);
//• ptr 是要调整的内存地址
//• size 调整之后新⼤⼩
//• realloc在调整内存空间的时候存在三种情况：
//1.假设你调整的空间刚好够，就返回原先旧地址
//2.假设后面的空间已经被占用（就是这块空间不够），会在堆区的空间中找一个新的空间，并且满足大小要求
//  接着将原来空间的数据拷贝一份到新的空间，然后释放旧的空间，最后返回新空间的地址
//3.调整失败，返回NULL
//int main()
//{
//	int* p = (int*)calloc(5, sizeof(int));
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	for (int i = 0; i < 5; i++)
//		p[i] = i + 1;
//	int* ptr = (int*)realloc(p, 10 * sizeof(int));
//	if (ptr != NULL)
//	{
//		//调整成功
//		p = ptr;
//		for (int i = 5; i < 10; i++)
//			p[i] = i + 1;
//		for (int i = 0; i < 10; i++)
//			printf("%d ", p[i]);
//		free(p);
//		p = NULL;
//	}
//	else
//	{
//		//调整失败
//		perror("realloc");
//		free(p);
//		p = NULL;
//	}
//  free(ptr);
//	return 0;
//}
//realloc可以完成和malloc一样的功能:
//realloc(NULL,20)===malloc(20) 意思都是随机找一块空间开辟20字节大小

//开辟空间有可能失败:
//int main()
//{
//	int* p = (int*)realloc(NULL,LLONG_MAX);
//	if (p == NULL)
//	{
//		perror("realloc");//没有足够的空间
//	}
//	return 0;
//}


//4. 常⻅的动态内存的错误
//4.1 对NULL指针的解引⽤操作
//int main()
//{
//	int* p = (int*)malloc(LLONG_MAX);
//	//需要对p判断或者断言一下:
//	//assert(p);
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	*p = 20;//如果p的值是NULL，就会有问题
//	free(p);
//}


//4.2 对动态开辟空间的越界访问
//int main()
//{
//	int i = 0;
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (NULL == p)
//	{
//		exit(EXIT_FAILURE);
//	}
//	for (i = 0; i <= 10; i++)
//	{
//		*(p + i) = i;//当i是10的时候越界访问
//	}
//	free(p);
//}


//4.3 对⾮动态开辟内存使⽤free释放
//int main()
//{
//	int a = 10;
//	int* p = &a;
//	free(p);//free只能释放动态申请的空间，就是堆上的空间，但是a是栈上的空间
// }


//4.4 使⽤free释放⼀块动态开辟内存的⼀部分
//int main()
//{
//	int* p = (int*)malloc(100);
//	p++;
//	free(p);//p不再指向动态内存的起始位置
//}


//4.5 对同⼀块动态内存多次释放
//int main()
//{
//	int* p = (int*)malloc(100);
//	free(p);
//	//不过此时在这里将p置为空指针的话
//	//p = NULL;
//	free(p);//这里就什么事都不做了
//}


//4.6 动态开辟内存忘记释放（内存泄漏）
//void test()
//{
//	int* p = (int*)malloc(100);
//	if (NULL != p)
//	{
//		*p = 20;
//	}//这里并没有释放内存
//}
//int main()
//{
//	test();
//	while (1);//如果程序一直不退出，malloc申请的空间也不释放，就一直占着，你找不到起始地址你用不了，别人也用不了
//}//内存是一种资源，需要回收

//malloc、realloc、calloc申请的内存，如果不想用的时候，可以用free释放
//如果没有使用free来释放，当前程序运行结束的时候，也会由操作系统回收

//尽量要做到:
//1.谁（函数）申请的空间谁释放(malloc和free成对出现)
//2.如果不能释放，要告诉(地址)使用的人，记得释放



//5. 动态内存经典笔试题分析
//5.1 题⽬1：
//void GetMemory(char* p)
//{
//	p = (char*)malloc(100);//函数内部申请动态内存空间，但没有释放内存：内存泄漏
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(str);//传值调用
//	strcpy(str, "hello world");//这里str还是NULL，对空指针进行解引用：程序崩溃
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}

//对上面题目的修改:
//void GetMemory(char** p)//用二级指针接受一级指针的地址
//{
//	*p = (char*)malloc(100);//解引用修改一级指针里面的内容,相当于告诉外面的str申请内存的起始地址
//}							//到时候不需要在这个函数内部释放空间，在外面释放也可以了
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str);//改成传址调用
//	strcpy(str, "hello world");//str指向申请内存的起始位置
//	printf(str);
//	free(str);//释放空间，此时str是野指针
//	str = NULL;//置空
//}
//int main()
//{
//	Test();
//	return 0;
//}


//5.2 题⽬2：
//char* GetMemory(void)
//{
//	char p[] = "hello world";//在栈区上创建的数组出了函数就会被销毁
//	return p;				//返回栈空间地址的问题，返回去就是野指针
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();//等GetMemory函数返回后，使用str指针去访问p数组，就是非法访问，因为p数组的内容已经还给了操作系统
//	printf(str);	//str是野指针
//}
//int main()
//{
//	Test();
//	return 0;
//}


//5.3 题⽬3：	
//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	strcpy(str, "hello");
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}
//这段代码唯一的问题就是忘记free了


//5.4 题⽬4：
//void Test(void)
//{
//	char* str = (char*)malloc(100);
//	strcpy(str, "hello");
//	free(str);
//	if (str != NULL)
//	{
//		strcpy(str, "world");//非法访问，str是野指针
//		printf(str);
//	}
//}
//int main()
//{
//	Test();
//	return 0;
//}


//6. 柔性数组
//结构中的最后⼀个元素允许是未知⼤⼩的数组，这就叫做『柔性数组』成员。
//6.1 柔性数组的特点：
//• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
//• sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
//• 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤
//⼩，以适应柔性数组的预期⼤⼩。
//struct st_type
//{
//	int i;
//	int arr[];//柔性数组成员（有些编译器里要写[0]）
//};			 //计算结构体大小的时候不包含柔性数组
//int main()
//{
//	struct st_type* p = (struct st_type*)malloc(sizeof(struct st_type) + 5 * sizeof(int));//申请空间
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	p->i = 100;
//	for (int i = 0; i < 5; i++)
//	{
//		p->arr[i] = i;
//		printf("%d ", p->arr[i]);
//	}
//	printf("\n");
//	//调整大小:
//	struct st_type* ptr = (struct st_type*)realloc(p, sizeof(struct st_type) + 10 * sizeof(int));
//	if (ptr != NULL)
//	{
//		p = ptr;
//		for (int i = 5; i < 10; i++)
//		{
//			p->arr[i] = i;
//			printf("%d ", p->arr[i]);
//		}
//	}
//	free(p);
//	p = NULL;
//	ptr = NULL;
//	return 0;
//}

//另一种写法(非柔性数组，但是有柔性数组的特点，可以改变数组大小):
//struct st_type
//{
//	int i;
//	int* arr;
//};
//int main()
//{
//	struct st_type* p = (struct st_type*)malloc(sizeof(struct st_type));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//我们可以为arr指向的地址开辟一块空间:
//	p->arr = (int*)malloc(5 * sizeof(int));
//	for (int i = 0; i < 5; i++)
//	{
//		p->arr[i] = i;
//		printf("%d ", p->arr[i]);
//	}
//	printf("\n");
//	//调整大小:
//	int*ptr = (int*)realloc(p->arr, 10 * sizeof(int));
//	if (ptr != NULL)
//	{
//		p->arr = ptr;
//		for (int i = 5; i < 10; i++)
//		{
//			p->arr[i] = i;
//			printf("%d ", p->arr[i]);
//		}
//	}
//	//释放内存:
//	free(p->arr);
//	free(p);
//	p = NULL;
//	ptr = NULL;
//	return 0;
//}

//上面两种方式都能实现同样的效果，但是第一种方式有两个好处:
//第⼀个好处是：⽅便内存释放
//如果我们的代码是在⼀个给别⼈⽤的函数中，你在⾥⾯做了⼆次内存分配，并把整个结构体返回给⽤
//⼾。⽤⼾调⽤free可以释放结构体，但是⽤⼾并不知道这个结构体内的成员也需要free，所以你不能
//指望⽤⼾来发现这个事。所以，如果我们把结构体的内存以及其成员要的内存⼀次性分配好了，并返
//回给⽤⼾⼀个结构体指针，⽤⼾做⼀次free就可以把所有的内存也给释放掉。
//第⼆个好处是：这样有利于访问速度.
//连续的内存有益于提⾼访问速度，也有益于减少内存碎⽚.
//原因是第一种方式是一次性开辟好空间int i和int arr[]的空间是连续的
//第二种方式是先开辟好int i和int* arr,然后再开辟arr所指向的空间，这两部分是割裂的，会产生内存碎片
